#include "stdafx.h"
#include "UModel.h"
#include "UFace.h"
#include "UStringUtils.h"
#include <string>
#include <boost/foreach.hpp>
using namespace std;

TModel::TModel() {
	triangles = NULL; //Prevent double free in setTriangles
	VBOARBVertexCount = -1;
	VBOARBTriangles = -1;
	VBOARBNormals = -1;
	VBOARBTexCoords = -1;
	setTriangles(new vector<TTriangle >);
}

TModel::~TModel() {
	if (triangles) delete triangles;
}

void TModel::generateARBData() {
	if(!triangles) return;
	VBOARBVertexCount = 3*triangles->size(); //3 vertices per triangle
	if(VBOARBVertexCount == 0) {
		cerr << "TMODEL: NOTICE: MODEL WILL SELF DESTRUCT IF NOT FILLED WITH TRIANGLES!\n";
		VBOARBVertexCount = -1;
		return;
	}
	GLfloat *data = new GLfloat[4*VBOARBVertexCount]; //max 4 GLfloat per vertex (u,v)=4 < (n|vertex)=3
	CLEAR_GL_ERROR_FLAGS;
	if(VBOARBTriangles != -1) glDeleteBuffersARB( 1, &VBOARBTriangles );
	glGenBuffersARB(1,&VBOARBTriangles);
	if (GLenum err = glGetError()) {
		string s = STRFORMAT("glGenBuffersARB error %x\n", (unsigned int)err);
		cerr << s;
		throw std::runtime_error(s);
	}
	if(VBOARBNormals != -1) glDeleteBuffersARB( 1, &VBOARBNormals );
	glGenBuffersARB(1,&VBOARBNormals);
	if (GLenum err = glGetError()) {
		string s = STRFORMAT("glGenBuffersARB error %x\n", (unsigned int)err);
		cerr << s;
		throw std::runtime_error(s);
	}
	if(VBOARBTexCoords != -1) glDeleteBuffersARB( 1, &VBOARBTexCoords );
	glGenBuffersARB(1,&VBOARBTexCoords);
	if (GLenum err = glGetError()) {
		string s = STRFORMAT("glGenBuffersARB error %x\n", (unsigned int)err);
		cerr << s;
		throw std::runtime_error(s);
	}

	uint32 j=0;
	BOOST_FOREACH(const TTriangle & i, *triangles) {
		data[j++] = i.v1.x;
		data[j++] = i.v1.y;
		data[j++] = i.v1.z;
		data[j++] = i.v2.x;
		data[j++] = i.v2.y;
		data[j++] = i.v2.z;
		data[j++] = i.v3.x;
		data[j++] = i.v3.y;
		data[j++] = i.v3.z;
	}
	glBindBufferARB(GL_ARRAY_BUFFER_ARB, VBOARBTriangles );
	if (GLenum err = glGetError()) {
		string s = STRFORMAT("glGenBuffersARB error %x\n", (unsigned int)err);
		cerr << s;
		throw std::runtime_error(s);
	}
	glBufferDataARB(GL_ARRAY_BUFFER_ARB,
	                VBOARBVertexCount *3* sizeof(GLfloat),
	                data,
	                GL_STATIC_DRAW_ARB
	               );
	if (GLenum err = glGetError()) {
		string s = STRFORMAT("glGenBuffersARB error %x\n", (unsigned int)err);
		cerr << s;
		throw std::runtime_error(s);
	}


	j=0;
	BOOST_FOREACH(const TTriangle & i, *triangles) {
		data[j++] = i.n1.x;
		data[j++] = i.n1.y;
		data[j++] = i.n1.z;
		data[j++] = i.n2.x;
		data[j++] = i.n2.y;
		data[j++] = i.n2.z;
		data[j++] = i.n3.x;
		data[j++] = i.n3.y;
		data[j++] = i.n3.z;
	}
	glBindBufferARB(GL_ARRAY_BUFFER_ARB, VBOARBNormals);
	if (GLenum err = glGetError()) {
		string s = STRFORMAT("glGenBuffersARB error %x\n", (unsigned int)err);
		cerr << s;
		throw std::runtime_error(s);
	}
	glBufferDataARB(GL_ARRAY_BUFFER_ARB,
	                VBOARBVertexCount * 3 * sizeof(GLfloat),
	                data,
	                GL_STATIC_DRAW_ARB
	               );
	if (GLenum err = glGetError()) {
		string s = STRFORMAT("glGenBuffersARB error %x\n", (unsigned int)err);
		cerr << s;
		throw std::runtime_error(s);
	}

	j=0;
	BOOST_FOREACH(const TTriangle & i, *triangles) {
		data[j++] = i.uv1.x;
		data[j++] = i.uv1.y;
		data[j++] = i.uv2.x;
		data[j++] = i.uv2.y;
		data[j++] = i.uv3.x;
		data[j++] = i.uv3.y;
	}
	glBindBufferARB(GL_ARRAY_BUFFER_ARB, VBOARBTexCoords);
	if (GLenum err = glGetError()) {
		string s = STRFORMAT("glGenBuffersARB error %x\n", (unsigned int)err);
		cerr << s;
		throw std::runtime_error(s);
	}
	glBufferDataARB(GL_ARRAY_BUFFER_ARB,
	                VBOARBVertexCount * 2 * sizeof(GLfloat),
	                data,
	                GL_STATIC_DRAW_ARB
	               );
	if (GLenum err = glGetError()) {
		string s = STRFORMAT("glGenBuffersARB error %x\n", (unsigned int)err);
		cerr << s;
		throw std::runtime_error(s);
	}

	delete data;
}

void TModel::calculateBoundingBox() {
	if(triangles->size()) {
		minPoint = (triangles->begin())->v1;
		maxPoint = minPoint;
		BOOST_FOREACH(const TTriangle & i, *triangles) {
			minPoint.x = min(i.v1.x,min(i.v2.x,min(i.v3.x,minPoint.x)));
			minPoint.y = min(i.v1.y,min(i.v2.y,min(i.v3.y,minPoint.y)));
			minPoint.z = min(i.v1.z,min(i.v2.z,min(i.v3.z,minPoint.z)));
			maxPoint.x = max(i.v1.x,max(i.v2.x,max(i.v3.x,maxPoint.x)));
			maxPoint.y = max(i.v1.y,max(i.v2.y,max(i.v3.y,maxPoint.y)));
			maxPoint.z = max(i.v1.z,max(i.v2.z,max(i.v3.z,maxPoint.z)));
		}
	}
}

void TModel::setTriangles( vector<TTriangle >* triangleList ) {
	if (triangles == triangleList) return;
	if (triangles) delete triangles;
	triangles = triangleList;
	calculateBoundingBox(); //Will be replaced by more specific bounding data later
	if (glBindBufferARB && glGenBuffersARB && glBufferDataARB) { //has hardware polygon pushing
		generateARBData();
		//delete triangles; FIXME: Memory leak!!!!!
		//triangles = NULL;
	}
}

void TModel::setTriangles( const vector<TTriangle >& triangleList ) {
	setTriangles(new vector<TTriangle>(triangleList));
}

uint32 TModel::vertexCount() {
	if (glBindBufferARB && glGenBuffersARB && glBufferDataARB)
		return VBOARBVertexCount;
	else
		return triangles->size();
}

TVector TModel::getMinPoint() {
	return minPoint;
}

TVector TModel::getMaxPoint() {
	return maxPoint;
}

TVector TModel::getFurthestPoint() {
	return TVector( max( sqrt(minPoint.x*minPoint.x), sqrt(maxPoint.x*maxPoint.x)), //abs() no verk wid floats?
	                max( sqrt(minPoint.y*minPoint.y), sqrt(maxPoint.y*maxPoint.y)),
	                max( sqrt(minPoint.z*minPoint.z), sqrt(maxPoint.z*maxPoint.z)));
}

void TModel::drawBoundingBox() {
	GLint polymode[2];
	GLfloat color[4];
	GLboolean light;
	GLboolean culling;
	glGetIntegerv(GL_POLYGON_MODE, polymode);
	glGetFloatv(GL_CURRENT_COLOR, color);
	glGetBooleanv(GL_LIGHTING, &light);
	glGetBooleanv(GL_CULL_FACE, &culling);
	glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
	glColor4f(1,0,1,1);
	glDisable(GL_LIGHTING);
	glDisable(GL_CULL_FACE);
	glBegin(GL_QUADS);
		glVertex3f(minPoint.x, minPoint.y, minPoint.z);
		glVertex3f(minPoint.x, minPoint.y, maxPoint.z);
		glVertex3f(maxPoint.x, minPoint.y, maxPoint.z);
		glVertex3f(maxPoint.x, minPoint.y, minPoint.z);

		glVertex3f(minPoint.x, maxPoint.y, minPoint.z);
		glVertex3f(minPoint.x, maxPoint.y, maxPoint.z);
		glVertex3f(maxPoint.x, maxPoint.y, maxPoint.z);
		glVertex3f(maxPoint.x, maxPoint.y, minPoint.z);

		glVertex3f(minPoint.x, minPoint.y, minPoint.z);
		glVertex3f(minPoint.x, maxPoint.y, minPoint.z);
		glVertex3f(maxPoint.x, maxPoint.y, minPoint.z);
		glVertex3f(maxPoint.x, minPoint.y, minPoint.z);

		glVertex3f(minPoint.x, minPoint.y, maxPoint.z);
		glVertex3f(minPoint.x, maxPoint.y, maxPoint.z);
		glVertex3f(maxPoint.x, maxPoint.y, maxPoint.z);
		glVertex3f(maxPoint.x, minPoint.y, maxPoint.z);

		glVertex3f(minPoint.x, minPoint.y, minPoint.z);
		glVertex3f(minPoint.x, minPoint.y, maxPoint.z);
		glVertex3f(minPoint.x, maxPoint.y, maxPoint.z);
		glVertex3f(minPoint.x, maxPoint.y, minPoint.z);

		glVertex3f(maxPoint.x, minPoint.y, minPoint.z);
		glVertex3f(maxPoint.x, minPoint.y, maxPoint.z);
		glVertex3f(maxPoint.x, maxPoint.y, maxPoint.z);
		glVertex3f(maxPoint.x, maxPoint.y, minPoint.z);
	glEnd();
	glColor4f(color[0],color[1],color[2], color[3]);
	glPolygonMode(GL_FRONT, polymode[0]);
	glPolygonMode(GL_BACK, polymode[1]);
	if(light) glEnable(GL_LIGHTING);
	if(culling) glEnable(GL_CULL_FACE);
}

void TModel::draw(uint32 DebugFlags) {
#ifdef DEBUG
	if(DebugFlags) drawBoundingBox();
#endif
	draw();
}

void TModel::draw() {
	glEnable(GL_TEXTURE_2D);
	glBindTexture(GL_TEXTURE_2D, Texture);
	if (glBindBufferARB && glGenBuffersARB && glBufferDataARB) {
		glEnableClientState(GL_VERTEX_ARRAY);
		glEnableClientState(GL_NORMAL_ARRAY);
		glEnableClientState(GL_TEXTURE_COORD_ARRAY);

		glBindBufferARB(GL_ARRAY_BUFFER_ARB, VBOARBTriangles);
		glVertexPointer(3,GL_FLOAT,0,NULL);

		glBindBufferARB(GL_ARRAY_BUFFER_ARB, VBOARBNormals);
		glNormalPointer(GL_FLOAT,0, NULL);

		glBindBufferARB(GL_ARRAY_BUFFER_ARB, VBOARBTexCoords);
		glTexCoordPointer(2, GL_FLOAT,0, NULL);

		glDrawArrays(GL_TRIANGLES,0, VBOARBVertexCount);

		glDisableClientState(GL_TEXTURE_COORD_ARRAY);
		glDisableClientState(GL_NORMAL_ARRAY);
		glDisableClientState(GL_VERTEX_ARRAY);
	}
	else
	{
		glBegin(GL_TRIANGLES);
			BOOST_FOREACH(const TTriangle & i, *triangles) {
				glNormal3f(i.n1.x, i.n1.y, i.n1.z);
				glTexCoord2f(i.uv1.x, i.uv1.y);
				glVertex3f(i.v1.x, i.v1.y, i.v1.z);

				glNormal3f(i.n2.x, i.n2.y, i.n2.z);
				glTexCoord2f(i.uv2.x, i.uv2.y);
				glVertex3f(i.v2.x, i.v2.y, i.v2.z);

				glNormal3f(i.n3.x, i.n3.y, i.n3.z);
				glTexCoord2f(i.uv3.x, i.uv3.y);
				glVertex3f(i.v3.x, i.v3.y, i.v3.z);
			}
		glEnd();
	}
	glDisable(GL_TEXTURE_2D);
}
